Перейти к основному содержимому

Графика и разработка игр на Python

Разработчику Архитектору

Tkinter и GUI

GUI в Python

Если задача требует создания полноценного приложения с кнопками, полями ввода, меню и другими элементами управления, необходимо использовать специализированные библиотеки для построения GUI.

Графический пользовательский интерфейс (Graphical User Interface) — это система, позволяющая пользователю взаимодействовать с программой через визуальные компоненты: окна, кнопки, списки, диаграммы и т.д. Реализация GUI в Python возможна благодаря нескольким основным библиотекам:

  • Tkinter — стандартная библиотека, интегрированная в Python, построенная на Tcl/Tk.
  • PyQt / PySide — привязки к фреймворку Qt, предоставляющие богатый набор виджетов.
  • Kivy — кроссплатформенная библиотека для мультитач-приложений и мобильных интерфейсов.
  • Dear PyGui, Flet, Eel — современные альтернативы с акцентом на производительность или web-подход.

Любое GUI-приложение следует событийно-ориентированной архитектуре:

  • Создаётся главное окно.
  • Добавляются виджеты (элементы управления).
  • Регистрируются обработчики событий (нажатие кнопки, ввод текста).
  • Запускается главный цикл (mainloop), который ожидает и распределяет события.

Пример на Tkinter

import tkinter as tk
from tkinter import messagebox

root = tk.Tk()
root.title("Пример GUI")
root.geometry("300x200")

label = tk.Label(root, text="Введите имя:")
label.pack(pady=10)

entry = tk.Entry(root)
entry.pack(pady=5)

def greet():
name = entry.get()
messagebox.showinfo("Приветствие", f"Привет, {name}!")

button = tk.Button(root, text="Приветствовать", command=greet)
button.pack(pady=10)

root.mainloop()

С развитием веб-технологий наблюдается рост популярности гибридных решений: например, использование Flask/FastAPI в качестве бэкенда и Electron-like обёрток (например, pywebview) для отображения интерфейса в браузере. Это позволяет применять современные фронтенд-фреймворки (React, Vue) вместе с Python.

Tkinter — это стандартный интерфейс языка Python к графической библиотеке Tk, построенной на основе Tcl (Tool Command Language). Он входит в состав стандартной библиотеки CPython и предоставляет доступ к кроссплатформенному набору средств для создания графических пользовательских интерфейсов (GUI). Tkinter представляет собой обёртку (binding) вокруг Tcl/Tk, что определяет как его особенности, так и ограничения.

В Python 3 Tkinter переименован в tkinter (с маленькой буквы), однако термин "Tkinter" закрепился как общее обозначение этой системы.

Архитектура Tkinter следует двухуровневой модели:

  • Python-уровень: код на Python, использующий классы и методы из модуля tkinter.
  • Tcl/Tk-уровень: низкоуровневый интерпретатор Tcl, в котором выполняются команды, генерируемые через Python API, и который отвечает за рендеринг виджетов и обработку событий.

Между этими уровнями существует мост (bridge), реализованный на уровне C в составе интерпретатора CPython. Этот мост обеспечивает:

  • Передачу вызовов от Python к Tcl.
  • Сериализацию аргументов в строковые команды Tcl.
  • Получение результатов выполнения и их преобразование обратно в Python-объекты.

Например, вызов:

button = tk.Button(root, text="OK")

…внутри преобразуется в TCL-команду вроде:

button .button1 -text "OK"

… которая выполняется в рамках внутреннего интерпретатора Tcl.

«Внутренний интерпретатор»? Из каких же тогда компонентов состоит Tkinter?

Приложение Tkinter состоит из интерпретатора Tcl, главного окна и виджетов. Python-код генерирует команды, которые передаются в Tcl-интерпретатор через мост на языке C. Интерпретатор управляет отрисовкой и обработкой событий операционной системы.

КомпонентНазначение
Интерпретатор TclВыполняет команды рисования и хранит состояние виджетов
Главное окно (Tk)Корневой контейнер приложения, инициализирует среду
Виджеты (Widgets)Графические элементы управления (кнопки, метки, поля)
Геометрические менеджерыСистемы размещения виджетов (pack, grid, place)
Событийный цикл (mainloop)Обрабатывает ввод пользователя и системные сообщения

Интерпретатор Tcl

Каждый экземпляр приложения Tkinter автоматически инициализирует встроенный интерпретатор Tcl. Этот интерпретатор управляет жизненным циклом GUI, хранит состояние всех виджетов, выполняет команды рисования, обрабатывает события операционной системы (через Tk).

Важно понимать, что все виджеты существуют в пространстве имён Tcl, а не в памяти Python. Python-объекты (например, tk.Button) являются лишь обёртками, ссылающимися на соответствующие Tcl-идентификаторы.


Инициализация приложения

Запуск программы требует создания экземпляра главного окна и запуска цикла событий.

import tkinter as tk

root = tk.Tk()
root.title("Приложение")
root.geometry("400x300")

root.mainloop()

Метод mainloop() запускает бесконечный цикл ожидания событий. Выполнение кода после этого вызова происходит только после закрытия окна.


Главное окно (Tk)

Класс tk.Tk() создаёт главное окно приложения и инициирует запуск Tcl-интерпретатора. Это первый и обязательный шаг в любом Tkinter-приложении:

root = tk.Tk()

При создании экземпляра Tk происходит запуск внутреннего интерпретатора Tcl, создание корневого окна в графической подсистеме (X11, Windows GDI, macOS Aqua) и инициализация event loop.

Управление может быть передано только одному экземпляру Tk. Попытка создать второй приведёт к ошибке, если первый не был уничтожен.


Виджеты (Widgets)

Виджет — это графический элемент интерфейса: кнопка, метка, поле ввода и т.д. Каждый виджет в Tkinter является подклассом базового класса Widget. Все виджеты наследуются от BaseWidget. То есть, цепочка такова:

BaseWidget → Widget → Mixins → конкретный тип (например, Button, Label).

Виджеты принадлежат иерархии, где каждый виджет имеет родителя (обычно окно или контейнер). Идентифицируются уникальным строковым именем (например, .frame1.button2), которое используется в Tcl.

Пример создания виджета:

label = tk.Label(parent, text="Текст")

Здесь parent — другой виджет (например, Frame или Tk). При создании виджет регистрируется в Tcl-интерпретаторе под уникальным именем.

Виджет Label служит для отображения текста или изображений в окне приложения. Элемент относится к статическим компонентам интерфейса и не предполагает прямого взаимодействия пользователя через клик или ввод текста. Основная задача заключается в информировании пользователя, заголовках разделов или выводе результатов вычислений.

Объект создаётся через класс tk.Label. Первый аргумент указывает родительский контейнер.

label = tk.Label(root, text="Приветствие", fg="blue", bg="white")
label.pack()

Таблица параметров Label:

ПараметрОписаниеПример значения
textТекст для отображения"Информация"
textvariableСвязь с объектом переменнойStringVar()
fontШрифт текста("Arial", 14, "bold")
fgЦвет текста (foreground)"red", "#FF0000"
bgЦвет фона (background)"yellow", "#FFFF00"
padxОтступ по горизонтали внутри виджета10
padyОтступ по вертикали внутри виджета5
widthШирина виджета в символах20
heightВысота виджета в строках2
wraplengthДлина строки для переноса текста (пиксели)100
justifyВыравнивание многострочного текстаLEFT, CENTER, RIGHT
anchorПозиционирование текста внутри виджетаN, S, E, W, CENTER
imageОбъект изображения для отображенияtk.PhotoImage
compoundРасположение текста относительно изображенияTOP, BOTTOM, LEFT, RIGHT
borderwidthШирина рамки вокруг виджета2
reliefСтиль рамкиFLAT, RAISED, SUNKEN, GROOVE
cursorФорма курсора при наведении"hand2", "dot"

Методы Label:

МетодОписание
config(**options)Изменяет параметры виджета динамически
configure(**options)Алиас для config
cget(option)Возвращает текущее значение параметра
destroy()Удаляет виджет из интерфейса
place(**options)Размещает виджет через менеджер place
pack(**options)Размещает виджет через менеджер pack
grid(**options)Размещает виджет через менеджер grid
bind(sequence, func)Привязывает функцию к событию
unbind(sequence)Удаляет привязку события
focus_set()Устанавливает фокус ввода на виджет

Пример использования Label:

import tkinter as tk

root = tk.Tk()

label_title = tk.Label(
root,
text="Заголовок окна",
font=("Arial", 16, "bold"),
fg="white",
bg="black",
padx=20,
pady=10
)
label_title.pack(pady=20)

label_info = tk.Label(
root,
text="Это информационная метка.\nТекст может быть многострочным.",
justify="left",
wraplength=200
)
label_info.pack()

root.mainloop()

Динамическое обновление текста

Изменение текста метки во время работы программы требует использования объекта StringVar. Прямое изменение параметра text через config также возможно, но переменные обеспечивают автоматическую синхронизацию.

import tkinter as tk

root = tk.Tk()

text_var = tk.StringVar()
text_var.set("Начальное значение")

label = tk.Label(root, textvariable=text_var)
label.pack()

def update_text():
text_var.set("Обновлённый текст")

button = tk.Button(root, text="Изменить", command=update_text)
button.pack()

root.mainloop()

Геометрические менеджеры

Размещение виджетов на экране осуществляется с помощью геометрических менеджеров — специальных систем управления компоновкой:

a) pack(). Располагает виджеты в рамках родительского контейнера в виде потока (сверху, снизу, слева, справа). Подходит для простых линейных интерфейсов.

widget.pack(side="top", fill="x", expand=True)

b) grid(). Организует виджеты в таблицу (строки и столбцы). Наиболее гибкий и часто используемый менеджер для сложных форм.

widget.grid(row=0, column=1, padx=5, pady=5)

c) place(). Позволяет задавать абсолютные или относительные координаты. Редко используется, так как нарушает адаптивность интерфейса.

widget.place(x=100, y=50)

Важно: внутри одного контейнера нельзя смешивать разные менеджеры.

Система размещает виджеты внутри контейнера с помощью менеджеров компоновки. Каждый контейнер использует один тип менеджера для своих дочерних элементов.

Менеджер pack размещает виджеты блоками относительно сторон контейнера. Подходит для простых линейных интерфейсов.

ПараметрОписание
sideСторона прикрепления (TOP, BOTTOM, LEFT, RIGHT)
fillРастягивание (X, Y, BOTH, NONE)
expandЗанятие свободного пространства (True/False)
padx, padyВнешние отступы от других виджетов
ipadx, ipadyВнутренние отступы внутри виджета
anchorПозиция виджета в свободном пространстве
label.pack(side="top", fill="x", expand=True)

Менеджер grid организует виджеты в таблицу из строк и столбцов. Позволяет создавать сложные формы.

ПараметрОписание
rowНомер строки (начинается с 0)
columnНомер столбца (начинается с 0)
rowspanКоличество занимаемых строк
columnspanКоличество занимаемых столбцов
stickyПрилипание к сторонам (N, S, E, W, NS, EW)
padx, padyОтступы между ячейками
label.grid(row=0, column=0, sticky="w", padx=10, pady=5)

Менеджер place задаёт абсолютные или относительные координаты. Требует ручного расчёта позиций.

ПараметрОписание
x, yАбсолютные координаты в пикселях
relx, relyОтносительные координаты (0.0 - 1.0)
width, heightAbsolute размеры
relwidth, relheightОтносительные размеры
anchorТочка привязки виджета к координатам
label.place(x=50, y=100, width=200, height=50)

Обработка событий

Tkinter следует парадигме событийно-ориентированного программирования (event-driven programming). Система работает в цикле, ожидая внешних воздействий (движение мыши, нажатие клавиш, изменения размера окна и т.д.).

И основной цикл называется mainloop.

Вызов:

root.mainloop()

Он запускает бесконечный цикл, в котором:

  • ОС отправляет сообщения (события) в Tk.
  • Tk преобразует их в Tcl-события.
  • Tcl вызывает зарегистрированные обработчики.
  • Управление возвращается в Python, если обработчик задан как Python-функция.

Цикл блокирует выполнение последующего кода до закрытия окна.

События связываются с виджетами через метод bind():

widget.bind("<Button-1>", handler)

События обозначаются в угловых скобках: <KeyPress>, <Motion>, <Configure> и т.д. Обработчик получает объект события (Event), содержащий детали: координаты, клавишу, время и др.

Для кнопок и других элементов управления также используется параметр command:

button = tk.Button(root, text="Click", command=handler)

Этот способ проще, но поддерживается только для виджетов, генерирующих предопределённые действия (например, клик по кнопке).

Библиотека tkinter.ttk (Themed Tk) предоставляет доступ к современным, стилизованным виджетам, использующим нативную тему операционной системы. Виджеты из ttk выглядят естественнее и интегрированнее:

from tkinter import ttk
button = ttk.Button(root, text="Современная кнопка")

ttk использует ту же архитектуру, но рендерит виджеты с использованием системных стилей (через engine themes). Однако не все виджеты имеют ttk-аналоги, и поведение может отличаться.

Tkinter не является потокобезопасным. Все вызовы, изменяющие GUI, должны выполняться из основного потока, где работает mainloop. Если фоновый поток должен обновить интерфейс, необходимо использовать:

  • Метод after(ms, func) — отложенное выполнение функции в основном потоке.
  • Очереди (queue.Queue) для передачи данных между потоками.

Пример безопасного обновления:

def update_label():
if not q.empty():
value = q.get()
label.config(text=value)
root.after(100, update_label) # Повтор через 100 мс

Альтернативно — использование asyncio с интеграцией через async-tkinter-loop, но это требует сторонних библиотек.

Tkinter — это тонкая прослойка между Python и Tcl/Tk. Его архитектура определяется историческими причинами: Tcl/Tk была одной из первых кроссплатформенных GUI-библиотек, и её интеграция в Python обеспечила быстрый доступ к графическим возможностям без необходимости переписывать всё на C.

Для сложных проектов рекомендуется рассмотреть PyQt/PySide или Kivy, но понимание устройства Tkinter помогает осознать фундаментальные принципы построения GUI-приложений в Python.

Виджет Label поддерживает привязку событий через метод bind. Это позволяет реагировать на наведение мыши, клики или клавиши.

Строки событий:

СобытиеОписание
<Button-1>Нажатие левой кнопки мыши
<Button-2>Нажатие средней кнопки мыши
<Button-3>Нажатие правой кнопки мыши
<Motion>Движение мыши внутри виджета
<Enter>Курсор мыши вошёл в область виджета
<Leave>Курсор мыши покинул область виджета
<Key>Нажатие любой клавиши (при фокусе)
<Configure>Изменение размера виджета
import tkinter as tk

root = tk.Tk()

label = tk.Label(root, text="Наведи на меня", bg="lightgray", width=20, height=5)
label.pack(pady=50)

def on_enter(event):
label.config(bg="lightblue", text="Я здесь")

def on_leave(event):
label.config(bg="lightgray", text="Наведи на меня")

def on_click(event):
label.config(text="Клик зарегистрирован", bg="lightgreen")

label.bind("<Enter>", on_enter)
label.bind("<Leave>", on_leave)
label.bind("<Button-1>", on_click)

root.mainloop()

Работа с изображениями

Виджет Label отображает графические файлы через объект PhotoImage. Поддерживаются форматы GIF, PGM, PPM. Для других форматов (PNG, JPG) требуется предварительная конвертация или использование библиотеки Pillow.

import tkinter as tk

root = tk.Tk()

# Загрузка изображения
photo = tk.PhotoImage(file="image.gif")

label = tk.Label(root, image=photo)
label.image = photo # Сохранение ссылки для сборщика мусора
label.pack()

root.mainloop()

Сохранение ссылки label.image = photo предотвращает удаление объекта сборщиком мусора Python. Отсутствие ссылки приводит к исчезновению изображения.


Шрифты

Модуль tkinter.font предоставляет средства управления шрифтами. Позволяет получать метрики и создавать конфигурации.

import tkinter as tk
import tkinter.font as tkFont

root = tk.Tk()

custom_font = tkFont.Font(family="Helvetica", size=12, weight="bold")

label = tk.Label(root, text="Стильный текст", font=custom_font)
label.pack()

# Получение метрик
metrics = custom_font.metrics()
print(metrics)

root.mainloop()

Параметры шрифта:

ПараметрОписание
familyНазвание шрифта (Arial, Times, Courier)
sizeРазмер в пунктах (положительное) или пикселях (отрицательное)
weightНасыщенность (normal, bold)
slantНаклон (roman, italic)
underlineПодчёркивание (True/False)
overstrikeЗачёркивание (True/False)

Классы переменных Tkinter связывают значения Python с виджетами. Изменение значения переменной автоматически обновляет интерфейс.

КлассТип данныхИспользование
StringVarСтрокаТекст в Label, Entry
IntVarЦелое числоЧисловые значения, флажки
DoubleVarЧисло с плавающей точкойТочные числа
BooleanVarБулево значениеЧекбоксы, переключатели
import tkinter as tk

root = tk.Tk()

var = tk.IntVar()
var.set(10)

label = tk.Label(root, textvariable=var)
label.pack()

def increment():
current = var.get()
var.set(current + 1)

button = tk.Button(root, text="+", command=increment)
button.pack()

root.mainloop()

Примеры реализации

Таймер обратного отсчёта

Использование метода after для планирования задач.

import tkinter as tk

def countdown(count):
label_text.set(f"Осталось: {count}")
if count > 0:
root.after(1000, countdown, count - 1)
else:
label_text.set("Время вышло")

root = tk.Tk()
root.geometry("300x200")

label_text = tk.StringVar()
label = tk.Label(root, textvariable=label_text, font=("Arial", 24))
label.pack(pady=50)

countdown(10)

root.mainloop()

Метка с рамкой и тенью

Комбинирование параметров для визуального оформления.

import tkinter as tk

root = tk.Tk()
root.config(bg="gray")

label = tk.Label(
root,
text="Эффектная метка",
font=("Arial", 16),
bg="white",
fg="black",
padx=20,
pady=10,
relief="raised",
borderwidth=4
)
label.pack(pady=50)

root.mainloop()

Выравнивание текста

Демонстрация параметров anchor и justify.

import tkinter as tk

root = tk.Tk()

text_long = "Это длинный текст для демонстрации выравнивания.\nОн занимает несколько строк."

# Выравнивание содержимого внутри метки
label1 = tk.Label(root, text=text_long, justify="left", anchor="w", width=40, bg="lightblue")
label1.pack(fill="x", padx=10, pady=5)

# Выравнивание самой метки в контейнере
label2 = tk.Label(root, text="Центр", width=10, bg="lightgreen")
label2.pack(anchor="center", pady=5)

label3 = tk.Label(root, text="Право", width=10, bg="lightyellow")
label3.pack(anchor="e", pady=5)

root.mainloop()

Смена изображений по клику

import tkinter as tk

def switch_image():
global current_img
if current_img == photo1:
label.config(image=photo2)
current_img = photo2
else:
label.config(image=photo1)
current_img = photo1

root = tk.Tk()

photo1 = tk.PhotoImage(file="img1.gif")
photo2 = tk.PhotoImage(file="img2.gif")
current_img = photo1

label = tk.Label(root, image=photo1)
label.pack(pady=20)
label.bind("<Button-1>", lambda e: switch_image())

root.mainloop()

Turtle

Черепашья графика

Графический интерфейс в языке программирования Python может быть реализован различными способами — от простой черепашьей графики до полноценных 2D-приложений и сложных GUI-систем. Сначала поговорим о простой графике.

В жизни каждого специалиста возникают ситуации-конфузы. Так вот, после многих лет разработки на языках C#, Java, JavaScript, Python я встречаю некую «черепашью графику» в Python, причём в курсе программирования для детей. У меня конфуз - что за черепашья графика? Почему графика? Что имеется в виду? GUI? 2D/3D? Вектор/растр?

Потом ещё веселее - объекты в «черепашьей графике». Объектно-ориентированное программирование? Да? Или нет?

Оказалось, что есть такое необычное изобретение в истории обучения программированию. Профессионалы учатся немного иначе, тогда как новичков и детей учат совсем простым вещам, о которых мы даже не задумываемся.

Так вот, черепашья графика (Turtle Graphics) - это визуальный способ программирования, при котором мы управляем виртуальной черепашкой, которая ползёт по экрану и рисует линии. Она как художник с карандашом: куда она ползёт — там остаётся след. Это простая 2D-графика, где учатся программировать через наглядные действия вроде «иди вперёд», «поверни налево», «подними карандаш» и т.д.

Название происходит от языка Logo, который был разработан в 1960-х годах для обучения детей программированию. В нём была физическая игрушечная черепаха на полу, которая двигалась по командам и рисовала мелом. Потом эта идея перешла в виртуальную черепашку на экране.

В Python модуль turtle — это современная реализация этой идеи. Он встроен в стандартную библиотеку, то есть не нужно ничего устанавливать — просто пишем:

import turtle

И начинаем рисовать.

Базовая структура скрипта включает импорт, создание объектов, выполнение команд и завершение работы.

import turtle

# Создание экрана
screen = turtle.Screen()

# Создание черепахи
pen = turtle.Turtle()

# Команды рисования
pen.forward(100)

# Завершение работы
turtle.done()

Модуль turtle входит в стандартную библиотеку Python и предоставляет простую, но мощную систему для работы с двумерной графикой. Основная идея заключается в концепции «черепахи» — абстрактного исполнителя, перемещающегося по координатной плоскости и оставляющего за собой след. Через последовательность команд можно управлять направлением движения, положением, цветом линии и другими параметрами.

Задумка следующая - на экране появляется маленькая стрелочка (или черепашка, если включить соответствующую форму). У неё есть:

  • Позиция (x, y) — где она находится.
  • Направление — куда смотрит (например, вверх, вправо).
  • Ручка (pen) — опущена (рисует) или поднята (не рисует).
  • Цвет линии, цвет заливки, толщина линии, скорость движения.

Задача как раз в управлении «черепашкой» при помощи команд.

Чтобы окно не закрывалось сразу, нужно добавлять в конец кода:

turtle.done()

Функция turtle.done() запускает главный цикл обработки событий. Окно остаётся открытым до момента закрытия пользователем. Отсутствие этого вызова приводит к мгновенному закрытию окна после выполнения скрипта.

Команды там следующие:

  • forward(100) - Черепашка идёт вперёд на 100 пикселей;
  • backward(50) - Назад на 50 пикселей;
  • left(90) - Поворачивает налево на 90 градусов;
  • right(45) - Направо на 45°;
  • penup() - Поднимает ручку — больше не рисует;
  • pendown() - Опускает ручку — снова рисует;
  • goto(x, y) - Мгновенно перемещается в точку (x, y);
  • color("red") - Устанавливает цвет ручки и заливки;
  • pencolor("blue") - Только цвет линии;
  • fillcolor("yellow") - Цвет заливки фигур;
  • begin_fill() / end_fill() - Начинает и заканчивает заливку области;
  • speed(5) - Скорость от 1 (медленно) до 10 (быстро), 0 — мгновенно;
  • shape("turtle") - Меняет внешний вид: "arrow", "circle", "turtle" и др.

Пример: рисование квадрата.

image-2.png

Это как цикл for в C#, только вместо изменения переменной мы меняем состояние объекта.

Хотя это не GUI с кнопками, это графика в математическом смысле: работа с координатной плоскостью, углами, фигурами, цветами, траекториями. Экран является координатной плоскостью, где центр (0,0) - середина экрана, X вправо, Y вверх, как в базовой математике.

Так рисуют геометрические фигуры, используя циклы и функции, можно даже фракталы, узоры, анимации.

Здесь каждая черепашка — объект класса Turtle.

t1 = turtle.Turtle()
t2 = turtle.Turtle()

t1.color("red")
t2.color("blue")

t1.goto(-100, 0)
t2.goto(100, 0)

Технически, модуль turtle содержит в себе класс Turtle и при помощи конструктора Turtle() создаётся черепашка как объект, имеющий свойства и поведение. Концепция как раз в том, чтобы использовать методы черепашки для управления и рисования фигур.

t1 и t2 образуют две независимые «кисти», которые могут двигаться параллельно, рисовать разными цветами, иметь разную скорость и выполнять разные программы. Это отличный способ показать детям (и напомнить взрослым), что объекты — это экземпляры класса с собственным состоянием и поведением.

Чтобы не писать 360 строк для круга, можно использовать цикл, и получить почти круг (технически это 360-угольник). Можно делать розетки, спирали, снежинки Коха — всё через повторение простых действий.

for _ in range(360):
t.forward(1)
t.left(1)

Пример - две черепахи навстречу.

t1 = turtle.Turtle()
t2 = turtle.Turtle()

t1.penup()
t1.goto(-200, 0)
t1.pendown()
t1.color("red")

t2.penup()
t2.goto(200, 0)
t2.pendown()
t2.color("blue")
t2.setheading(180) # смотрит влево

# Ползут навстречу
for _ in range(100):
t1.forward(2)
t2.forward(2)

Так можно наглядно увидеть, что каждый объект живёт своей жизнью.

В объектно-ориентированной парадигме turtle представляет собой класс Turtle, экземпляры которого обладают состоянием (позиция, угол поворота, цвет пера, видимость) и поведением (методы перемещения, рисования, изменения стиля). Каждая черепаха работает в рамках общего холста — экземпляра класса Screen, который управляет окном вывода, фоном и событиями.

Черепаха может быть склонирована, переопределена или инкапсулирована в пользовательские классы, что позволяет строить более сложные графические системы.

Система координат в turtle — прямоугольная, с началом в центре окна. Положительное направление оси X — вправо, оси Y — вверх. Размеры окна задаются явно через метод setup(width, height) класса Screen. Координаты могут быть абсолютными (например, goto(x, y)) или относительными (например, forward(distance)).

Ориентация угла измеряется в градусах против часовой стрелки от положительного направления оси X. Поворот осуществляется командами left(angle) и right(angle).

Модуль поддерживает широкий спектр способов задания цветов:

  • Строковые имена: "red", "blue", "green" и др.
  • RGB-значения в формате кортежа (r, g, b), где каждое значение — число от 0 до 1 (или от 0 до 255 при активации режима colormode(255)).
  • HEX-коды: "#FF5733".

Для заливки фигур используется комбинация методов begin_fill() и end_fill(), которые фиксируют контур и применяют текущий цвет заполнения.

Например, можно нарисовать сердечко:

import turtle
import math

t = turtle.Turtle()
t.speed(0)
screen = turtle.Screen()
screen.bgcolor("white")

t.begin_fill()
t.fillcolor("red")

t.left(50)
t.forward(133)
t.circle(50, 200)
t.right(140)
t.circle(50, 200)
t.forward(133)

t.end_fill()

t.penup()

t.hideturtle()
turtle.done()

Анимация в turtle реализуется через последовательное изменение состояния черепахи с контролем скорости выполнения. Метод speed(n) позволяет установить скорость от 0 (мгновенно) до 10 (медленно). Для плавной анимации используются циклы с задержками, хотя сам модуль не является реалтайм-движком.

Обработка событий (например, нажатия клавиш, кликов мыши) осуществляется через регистрацию обработчиков:

def move_forward():
t.forward(10)

screen.onkey(move_forward, "Up")
screen.listen()

Здесь важно понимать, что turtle использует внутренний event loop, запускаемый вызовом screen.mainloop() — без него события не будут обрабатываться.

Поэтому всегда нужно добавлять в код следующее:

screen.listen()  # ← ВАЖНО! Без этого не будет работать

К примеру, так можно реализовать управление:

import turtle

screen = turtle.Screen()
screen.listen()

t = turtle.Turtle()
t.shape("turtle")
t.color("green")

def go_up():
t.setheading(90)
t.forward(20)

def go_down():
t.setheading(270)
t.forward(20)

def go_left():
t.setheading(180)
t.forward(20)

def go_right():
t.setheading(0)
t.forward(20)

screen.onkey(go_up, "Up")
screen.onkey(go_down, "Down")
screen.onkey(go_left, "Left")
screen.onkey(go_right, "Right")
screen.onkey(screen.bye, "q")

print("Управление: стрелки — движение, Q — выход")

turtle — однопоточная система; длительные операции блокируют интерфейс, и этот модуль не предназначен для производительных графических задач. Использует Tkinter в качестве бэкенда, что делает его зависимым от наличия GUI-среды. Подходит для обучения, прототипирования и демонстрации алгоритмов, но не для промышленных приложений.


Класс Screen

Объект Screen управляет окном приложения. Через него задаются размеры, цвет фона, заголовок и обработчики событий.

МетодПараметрыОписание
turtle.Screen()Создаёт объект главного окна
setup(width, height)числаУстанавливает размеры окна в пикселях
title(string)строкаЗадаёт заголовок окна
bgcolor(color)цветУстанавливает цвет фона
bgpic(picname)путь к файлуУстанавливает изображение фона
mode(mode)"standard", "logo", "parent"Выбирает режим координат и углов
tracer(n)числоУправляет анимацией отрисовки (0 выключает)
update()Обновляет экран при выключенном трассировщике
exitonclick()Закрывает окно по клику
bye()Закрывает окно программно

Экран регистрирует нажатия клавиш и клики мыши. Обработчики событий представляют собой функции, вызываемые при наступлении события.

def move():
pen.forward(50)

screen.onkey(move, "space")
screen.listen()

Вызов screen.listen() активирует прослушивание событий клавиатуры. Без этого вызова обработчики не срабатывают.


Класс Turtle

Объект Turtle представляет собой исполнителя. Каждый экземпляр класса обладает собственным состоянием: координатами, углом поворота, цветом пера, видимостью.

Движение и положение:

МетодПараметрыОписание
forward(distance)числоДвижение вперёд на указанное расстояние
backward(distance)числоДвижение назад
right(angle)числоПоворот направо на угол в градусах
left(angle)числоПоворот налево на угол в градусах
goto(x, y)числаПеремещение в точку с координатами (x, y)
setx(x)числоУстановка координаты X
sety(y)числоУстановка координаты Y
setheading(angle)числоУстановка абсолютного угла направления
home()Возврат в центр (0, 0) и направление вверх
circle(radius)числоРисование круга заданного радиуса
dot(size, color)число, цветРисование точки
stamp()Оставляет отпечаток черепахи на холсте
clearstamp(id)числоУдаляет отпечаток по идентификатору
position()Возвращает текущие координаты (x, y)
heading()Возвращает текущий угол поворота
distance(obj)объектВозвращает расстояние до другого объекта

Управление пером:

МетодПараметрыОписание
pendown()Опускает перо (рисует при движении)
penup()Поднимает перо (не рисует при движении)
pensize(width)числоУстанавливает толщину линии
pencolor(color)цветУстанавливает цвет линии
fillcolor(color)цветУстанавливает цвет заливки
color(color)цветУстанавливает цвет пера и заливки
begin_fill()Начинает запись контура для заливки
end_fill()Завершает заливку замкнутого контура
filling()Возвращает статус заливки (True/False)
reset()Сбрасывает состояние черепахи и очищает экран
clear()Очищает рисунки, сохраняет положение
undo()Отменяет последнее действие
speed(value)0-10Устанавливает скорость (0 — мгновенно)
hideturtle()Скрывает курсор черепахи
showturtle()Показывает курсор черепахи
isvisible()Возвращает видимость курсора

Координатная система

По умолчанию используется декартова система координат. Начало отсчёта (0, 0) находится в центре окна. Ось X направлена вправо, ось Y направлена вверх. Углы измеряются в градусах. Нулевой угол направлен вправо (восток), отсчёт идёт против часовой стрелки.

Режим standard устанавливает направление 0 градусов вправо. Режим logo устанавливает направление 0 градусов вверх.

Координаты принимают целые и дробные значения. Метод goto() перемещает объект в указанную точку. Методы setx() и sety() меняют одну из координат.


Обработка ввода и событий

Система событий реагирует на действия пользователя. Регистрация обработчиков происходит через объект Screen.

Метод onkey(fun, key) связывает функцию с клавишей. Срабатывание происходит при отпускании клавиши. Метод onkeypress(fun, key) срабатывает при нажатии и удержании (требует вызова listen()).

Регистр символов имеет значение. Клавиша "A" и клавиша "a" представляют собой разные события. Для букв нижнего регистра используется строчное обозначение. Для специальных клавиш используются строковые константы.

КлавишаОбозначение в коде
Пробел"space"
Вверх"Up"
Вниз"Down"
Влево"Left"
Вправо"Right"
Enter"Return"
Esc"Escape"
Tab"Tab"
Backspace"BackSpace"
Delete"Delete"
Home"Home"
End"End"
Page Up"Page_Up"
Page Down"Page_Down"
F1 - F12"F1" ... "F12"
Буквы"a" ... "z", "A" ... "Z"
Цифры"0" ... "9"
Плюс"plus"
Минус"minus"
Точка"period"

Пример регистрации клавиш:

def move_up():
pen.setheading(90)
pen.forward(20)

screen.onkey(move_up, "Up")
screen.onkey(move_up, "w")
screen.listen()

Обработка мыши позволяет реагировать на клики в любом месте экрана или на объектах.

МетодОписание
onclick(fun)Вызывает функцию при клике на черепаху
onrelease(fun)Вызывает функцию при отпускании кнопки на черепахе
ondrag(fun)Вызывает функцию при перетаскивании черепахи
onscreenclick(fun)Вызывает функцию при клике по экрану

Функция обработчика клика получает координаты события x и y.

def go_to(x, y):
pen.goto(x, y)

screen.onscreenclick(go_to)

Графика и внешний вид

Система поддерживает несколько форматов задания цвета.

  1. Строковые имена: "red", "green", "blue", "yellow", "black", "white", "purple", "orange" и другие стандартные названия.
  2. RGB кортеж: (r, g, b). Значения находятся в диапазоне от 0 до 1 по умолчанию.
  3. RGB 255: Требуется вызов screen.colormode(255). Значения находятся в диапазоне от 0 до 255.
  4. HEX: Строки вида "#FF5733".

Пример настройки цвета:

screen.colormode(255)
pen.pencolor((255, 0, 0)) # Красный
pen.fillcolor("#00FF00") # Зелёный

Курсор черепахи имеет форму. Доступны встроенные формы и пользовательские изображения.

ФормаОбозначение
Стрелка"arrow"
Черепаха"turtle"
Круг"circle"
Квадрат"square"
Треугольник"triangle"
Классическая"classic"

Метод shape(name) меняет внешний вид курсора.

Использование собственных изображений требует регистрации формы в объекте экрана. Файл изображения должен находиться в рабочей директории или путь должен быть указан полностью. Поддерживаются форматы GIF, PNG (зависит от версии Tkinter).

screen.register_shape("ship.gif")
pen.shape("ship.gif")

Изображение становится доступным для всех черепах после регистрации. Центр изображения совпадает с координатами черепахи.

Модуль позволяет выводить текст на холст через метод write().

ПараметрОписание
argТекст для вывода
moveBoolean. Перемещает ли черепаху после вывода
alignВыравнивание: "left", "center", "right"
fontКортеж (шрифт, размер, стиль)

Пример вывода текста:

pen.write("Старт", align="center", font=("Arial", 16, "bold"))

Стили шрифта: "normal", "bold", "italic", "bold italic".


Логика и циклы

Использование циклов позволяет создавать повторяющиеся узоры и фигуры. Конструкция for i in range() задаёт количество повторений.

Сумма внешних углов любого замкнутого многоугольника равна 360 градусам. Угол поворота для правильного многоугольника вычисляется как 360, делённое на количество сторон.

sides = 6
angle = 360 / sides

for i in range(sides):
pen.forward(100)
pen.right(angle)

Изменение расстояния или угла внутри цикла создаёт спираль.

for i in range(100):
pen.forward(i * 2)
pen.right(90)

Модуль random генерирует случайные числа для вариативности рисунка.

import random

colors = ["red", "blue", "green", "yellow"]

for i in range(50):
pen.pencolor(random.choice(colors))
pen.forward(random.randint(50, 100))
pen.right(random.randint(0, 360))

Скрипт реализует движение объекта стрелками клавиатуры.

import turtle

screen = turtle.Screen()
screen.setup(500, 500)
screen.title("Управление")

player = turtle.Turtle()
player.shape("turtle")
player.penup()
player.speed(0)

def move_up():
player.setheading(90)
player.forward(20)

def move_down():
player.setheading(270)
player.forward(20)

def move_left():
player.setheading(180)
player.forward(20)

def move_right():
player.setheading(0)
player.forward(20)

screen.onkey(move_up, "Up")
screen.onkey(move_down, "Down")
screen.onkey(move_left, "Left")
screen.onkey(move_right, "Right")
screen.listen()

turtle.done()

Пример создания закрашенного круга.

import turtle

screen = turtle.Screen()
pen = turtle.Turtle()

pen.fillcolor("blue")
pen.begin_fill()
pen.circle(50)
pen.end_fill()

turtle.done()

Программа рисует линии вслед за курсором мыши при нажатой кнопке.

import turtle

screen = turtle.Screen()
pen = turtle.Turtle()
pen.speed(0)

def drag(x, y):
pen.ondrag(None) # Блокировка рекурсии
pen.goto(x, y)
pen.ondrag(drag) # Возврат обработчика

pen.ondrag(drag)
screen.listen()
turtle.done()

Метод ontimer(fun, delay) запускает функцию через указанный интервал времени в миллисекундах. Это создаёт эффект анимации без блокировки цикла.

import turtle

screen = turtle.Screen()
pen = turtle.Turtle()
angle = 0

def rotate():
global angle
pen.clear()
pen.setheading(angle)
pen.forward(50)
pen.stamp()
angle += 10
screen.ontimer(rotate, 100) # Запуск через 100 мс

pen.speed(0)
rotate()
turtle.done()

Проверка расстояния между объектами позволяет реализовать простую коллизию.

import turtle
import random

screen = turtle.Screen()
player = turtle.Turtle()
target = turtle.Turtle()

player.shape("turtle")
player.penup()
target.shape("circle")
target.color("red")
target.penup()
target.goto(random.randint(-200, 200), random.randint(-200, 200))

def move():
player.forward(20)
if player.distance(target) < 20:
target.goto(random.randint(-200, 200), random.randint(-200, 200))

screen.onkey(move, "space")
screen.listen()
turtle.done()

Отрисовка каждого кадра анимации может замедлять работу программы. Метод tracer(0) отключает автоматическое обновление экрана. Обновление происходит только при вызове screen.update().

screen.tracer(0)

for i in range(100):
pen.forward(1)
# Экран не обновляется здесь

screen.update() # Обновление один раз в конце

Этот подход устраняет мерцание и повышает производительность при большом количестве объектов.


Работа с несколькими черепахами

Создание нескольких экземпляров класса Turtle позволяет управлять независимыми объектами.

t1 = turtle.Turtle()
t2 = turtle.Turtle()

t1.color("red")
t2.color("blue")

t1.goto(-50, 0)
t2.goto(50, 0)

t1.circle(30)
t2.circle(30)

Каждая черепаха хранит своё состояние независимо. Команды, отправленные одному объекту, не влияют на другие.


Сохранение и экспорт

Модуль turtle не имеет встроенной функции сохранения холста в файл изображения напрямую через стандартный API. Для сохранения результата используется скриншот операционной системы или сторонние библиотеки захвата экрана. Состояние программы (позиции, переменные) сохраняется через стандартные средства Python (модуль pickle или запись в файл).


Ограничения среды

Библиотека зависит от Tkinter. Запуск скрипта требует наличия графической среды. На серверах без графического интерфейса (headless) выполнение кода вызовет ошибку. Производительность ограничена возможностями интерпретатора Python и скоростью отрисовки Tkinter. Обработка большого количества объектов (более 100-200 активных спрайтов) приводит к снижению частоты кадров. Для сложных игр рекомендуется использовать специализированные движки.


Разработка игр

Основы разработки игр на Python

Python не является основным языком в индустрии разработки игр. Его динамическая типизация, интерпретируемая природа и относительно низкая производительность делают его малопригодным для создания ресурсоёмких проектов — таких как AAA-игры или масштабные многопользовательские онлайн-платформы. Однако Python остаётся мощным инструментом в контексте обучения, прототипирования, скриптования игровой логики и реализации простых 2D-приложений.

Для этих целей существует ряд библиотек, позволяющих организовать графический вывод, обработку ввода, аудиосопровождение и базовую физику. Наиболее известной и долгоживущей из них является Pygame — кроссплатформенная библиотека, построенная на основе SDL (Simple DirectMedia Layer). Она предоставляет минимально необходимый набор средств для создания 2D-игр и используется преимущественно в образовательных целях, а также в инди-проектах небольшого масштаба.

Python не предназначен для создания игровых движков уровня Unreal Engine или Unity. Тем не менее, он может эффективно применяться как средство обучения основам геймдева, анализа алгоритмов поведения персонажей, реализации ИИ в играх, а также как платформа для быстрого прототипирования механик.

Что касается других технологий, то Godot поддерживает язык GDScript, синтаксически близкий к Python, но это не Python. Начиная с версии 4.0, Godot также предлагает официальную поддержку Python через модуль GDExtension (на базе Cython), однако использование Python в Godot — скорее исключение, чем правило. Основная экосистема Godot ориентирована на GDScript, C# и C++. Таким образом, утверждение о том, что Godot «использует Python» — упрощение, не соответствующее действительности в полной мере.

В рамках данной главы мы сосредоточимся на Pygame — наиболее доступной и документированной библиотеке для разработки 2D-игр на чистом Python.

Pygame — это набор модулей Python, предоставляющий доступ к низкоуровневым функциям мультимедиа через привязки к библиотеке SDL. Он позволяет работать с графикой, звуком, вводом с клавиатуры, мыши и геймпадов, а также обеспечивает базовые средства для обработки времени и столкновений.

Библиотека не является игровым движком в строгом смысле слова. У неё отсутствуют встроенные системы анимаций, физики, сцен, шейдеров или редактор ресурсов. Вместо этого Pygame предоставляет низкоуровневые примитивы, которые разработчик должен компоновать самостоятельно, реализуя игровую логику «с нуля».

Тем не менее, именно эта особенность делает Pygame ценным инструментом для обучения: он заставляет разработчика понимать внутренние механизмы работы игрового цикла, обработки событий и управления состоянием объектов.


Pygame

Pygame представляет собой набор модулей Python, предоставляющий доступ к низкоуровневым функциям мультимедиа через привязки к библиотеке SDL (Simple DirectMedia Layer). Библиотека обеспечивает работу с графикой, звуком, вводом с клавиатуры, мыши и геймпадов. Система включает средства обработки времени и обнаружения столкновений.

Библиотека строится на основе SDL, написанной на языке C. Python-код выступает обёрткой, передающей команды в низкоуровневую среду. Это определяет производительность и кроссплатформенность. Поддерживаются операционные системы Windows, macOS, Linux.

Установка выполняется через менеджер пакетов pip:

pip install pygame

Проверка установки возможна через вывод версии модуля:

import pygame
print(pygame.ver)

Любое приложение на Pygame содержит базовый набор вызовов. Отсутствие любого из них приводит к ошибке выполнения или некорректной работе графического интерфейса.

  1. Импорт модуля: import pygame.
  2. Инициализация: pygame.init(). Запускает внутренние подсистемы (видео, аудио, ввод).
  3. Создание окна: pygame.display.set_mode(). Возвращает объект поверхности экрана.
  4. Игровой цикл: Бесконечный цикл while, обрабатывающий события и обновляющий кадр.
  5. Завершение: pygame.quit() и выход из интерпретатора.

Пример минимальной структуры:

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Окно игры")

running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False

screen.fill((0, 0, 0))
pygame.display.flip()

pygame.quit()

Библиотека разделена на логические модули. Каждый модуль отвечает за конкретную задачу.

МодульНазначениеКлючевые объекты
pygame.displayУправление окном и экраномSurface, set_mode, flip, update
pygame.drawРисование примитивовcircle, rect, line, polygon
pygame.fontРабота со шрифтами и текстомFont, render, SysFont
pygame.eventОбработка событий вводаget, poll, QUIT, KEYDOWN
pygame.timeКонтроль времени и кадровClock, tick, get_ticks
pygame.imageЗагрузка и сохранение изображенийload, save, fromstring
pygame.mixerВоспроизведение звукаSound, music, play, stop
pygame.spriteУправление игровыми объектамиSprite, Group, collide
pygame.rectПрямоугольники и коллизииRect, colliderect, move
pygame.keyСостояние клавиатурыget_pressed, name
pygame.mouseСостояние мышиget_pos, get_pressed, set_visible

Вызов pygame.init() активирует все доступные модули. Существует возможность инициализации отдельных подсистем для экономии ресурсов, например pygame.display.init() или pygame.mixer.init(). Функция pygame.get_init() возвращает статус инициализации. Функция pygame.quit() выключает все модули.

Окно и поверхности (Surfaces)

Модуль display контролирует графическое окно. Центральным объектом выступает поверхность (Surface), возвращаемая функцией set_mode().

Функция set_mode(size, flags=0, depth=0) принимает кортеж размеров (ширина, высота). Флаги определяют режим отображения.

ФлагОписание
pygame.FULLSCREENРазворачивает окно на весь экран
pygame.RESIZABLEПозволяет пользователю менять размер окна
pygame.NOFRAMEУбирает рамку и заголовок окна
pygame.HIDDENСоздаёт окно в скрытом режиме

Центральным понятием в Pygame является поверхность (Surface) — двумерный массив пикселей, представляющий собой область рисования. Каждое изображение, текст или графический элемент в Pygame является экземпляром pygame.Surface.

# Создание новой поверхности размером 100x100 с прозрачностью
surf = pygame.Surface((100, 100), pygame.SRCALPHA)
surf.fill((255, 0, 0, 128)) # Красный цвет с полупрозрачностью

Главное окно игры создаётся с помощью pygame.display.set_mode(), которое возвращает объект Surface, связанный с экраном. Все последующие операции рисования (blitting) выполняются на этой или других поверхностях.

import pygame
screen = pygame.display.set_mode((800, 600))

Поверхности могут быть прозрачными (с альфа-каналом), иметь цветовой ключ (colorkey) для маскирования фона, а также подвергаться трансформациям: масштабированию, повороту, отражению.

Изменения на поверхности не отображаются автоматически. Требуется явный вызов функции обновления.

ФункцияОписание
pygame.display.flip()Обновляет весь экран. Подходит для двойной буферизации
pygame.display.update(rectangle)Обновляет указанную область экрана. Экономит ресурсы

Заголовок и иконка тоже могут быть изменены:

pygame.display.set_caption("Название игры")
icon = pygame.image.load("icon.png")
pygame.display.set_icon(icon)

Координатная система

В Pygame начало координат (0, 0) находится в левом верхнем углу экрана. Ось X направлена вправо. Ось Y направлена вниз.

Это отличается от стандартной математической системы, где Y направлен вверх. При расчёте физики необходимо учитывать инверсию оси Y.

Размеры окна задаются в пикселях. Координаты объектов хранятся в целых числах. Дробные координаты возможны внутри логики игры, но отрисовка требует целочисленных значений.


Рисование

Модуль draw содержит функции для рисования геометрических фигур непосредственно на поверхности.

ФункцияПараметрыОписание
rectsurface, color, rect, width=0Рисует прямоугольник. width=0 заполняет фигуру
polygonsurface, color, points, width=0Рисует многоугольник по списку точек
circlesurface, color, center, radius, width=0Рисует круг
ellipsesurface, color, rect, width=0Рисует эллипс внутри прямоугольника
arcsurface, color, rect, start_angle, stop_angle, width=1Рисует дугу
linesurface, color, start_pos, end_pos, width=1Рисует линию между двумя точками
linessurface, color, closed, points, width=1Рисует набор соединённых линий
aalinesurface, color, start_pos, end_pos, blend=1Рисует сглаженную линию
aalinessurface, color, closed, points, blend=1Рисует набор сглаженных линий

Пример рисования фигуры:

pygame.draw.rect(screen, (0, 255, 0), (50, 50, 100, 100))
pygame.draw.circle(screen, (0, 0, 255), (200, 200), 50)

Класс pygame.Rect хранит координаты и размеры прямоугольной области. Используется для позиционирования и проверки столкновений.

# Из координат и размеров
rect = pygame.Rect(x, y, width, height)
# Из поверхности
rect = surface.get_rect()
# Из кортежа
rect = pygame.Rect((10, 10, 50, 50))

Объект Rect обладает набором атрибутов для доступа к координатам сторон и углов.

АтрибутОписание
x, yКоординаты левого верхнего угла
width, heightРазмеры прямоугольника
top, left, bottom, rightКоординаты сторон
center, centerx, centeryКоординаты центра
topleft, topright, bottomleft, bottomrightКоординаты углов
midtop, midleft, midbottom, midrightКоординаты середин сторон
sizeКортеж (width, height)
w, hКороткие aliases для width и height

Методы Rect:

МетодОписание
colliderect(other_rect)Возвращает True при пересечении с другим Rect
collidepoint(x, y)Возвращает True, если точка внутри Rect
collidelist(list)Проверяет пересечение со списком Rect
move(x, y)Возвращает новый Rect, смещённый на x, y
move_ip(x, y)Смещает текущий Rect на x, y (in place)
clamp(other_rect)Перемещает Rect внутрь другого Rect
clip(other_rect)Возвращает пересечение двух Rect
union(other_rect)Возвращает объединение двух Rect
unionall(list)Возвращает объединение со списком Rect
fit(other_rect)Масштабирует и перемещает Rect внутрь другого
normalize()Исправляет отрицательную ширину или высоту

Текст и шрифты (font)

Модуль font позволяет рендерить текст на поверхностях. Текст в Pygame является изображением.

# Системный шрифт
font = pygame.font.SysFont("Arial", 24)
# Загрузка из файла
font = pygame.font.Font("file.ttf", 24)
# Шрифт по умолчанию
font = pygame.font.Font(None, 24)

Метод render(text, antialias, color, background=None) создаёт поверхность с текстом.

ПараметрОписание
textСтрока для отображения
antialiasBoolean. Сглаживание границ букв
colorЦвет текста (RGB или RGBA)
backgroundЦвет фона текста (опционально)

Пример вывода текста:

text_surface = font.render("Счёт: 10", True, (255, 255, 255))
screen.blit(text_surface, (10, 10))

Доступные свойства Font:

СвойствоОписание
get_height()Высота символов в пикселях
get_linesize()Высота строки
get_ascent()Подъём шрифта над базовой линией
get_descent()Спуск шрифта под базовую линию
size(text)Возвращает кортеж (width, height) текста
metrics(text)Возвращает метрики для каждого символа
set_bold(bool)Устанавливает жирное начертание
set_italic(bool)Устанавливает курсив
set_underline(bool)Устанавливает подчёркивание
get_bold(), get_italic(), get_underline()Возвращают статус стилей

События и ввод (event)

Система событий обрабатывает действия пользователя и системы. События помещаются в очередь.

for event in pygame.event.get():
# Обработка

Функция pygame.event.get() возвращает список событий за последний кадр. Функция pygame.event.poll() возвращает одно событие или pygame.NOEVENT.

Типы событий:

ТипОписание
pygame.QUITПользователь нажал кнопку закрытия окна
pygame.KEYDOWNКлавиша нажата
pygame.KEYUPКлавиша отпущена
pygame.MOUSEMOTIONДвижение мыши
pygame.MOUSEBUTTONDOWNНажатие кнопки мыши
pygame.MOUSEBUTTONUPОтпускание кнопки мыши
pygame.JOYAXISMOTIONДвижение оси геймпада
pygame.JOYBUTTONDOWNНажатие кнопки геймпада

Событие KEYDOWN содержит атрибут key (код клавиши) и mod (модификаторы Shift, Ctrl).

if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
jump()
if event.key == pygame.K_ESCAPE:
running = False

Постоянное состояние клавиш проверяется через pygame.key.get_pressed(). Возвращает кортеж булевых значений для всех клавиш.

keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.x -= speed

Событие MOUSEBUTTONDOWN содержит атрибут button (1 - левая, 3 - правая, 2 - колесо). Атрибут pos содержит координаты (x, y).

if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
shoot(event.pos)

Текущая позиция мыши: pygame.mouse.get_pos(). Состояние кнопок: pygame.mouse.get_pressed().

Для геймпадов же требуется инициализация модуля pygame.joystick.

pygame.joystick.init()
joystick_count = pygame.joystick.get_count()
if joystick_count > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()

В цикле событий обрабатываются JOYAXISMOTION и JOYBUTTONDOWN. Оси возвращают значения от -1.0 до 1.0.


Время и частота кадров (time)

Модуль time контролирует скорость игры.

Объект pygame.time.Clock() регулирует частоту кадров.

clock = pygame.time.Clock()
# В конце цикла
clock.tick(60) # Ограничивает цикл 60 кадрами в секунду

Метод tick(framerate) задерживает выполнение, чтобы достичь указанной частоты. Возвращает время в миллисекундах с прошлого вызова.

Функция pygame.time.get_ticks() возвращает время в миллисекундах с момента инициализации pygame.init(). Используется для таймеров.

start_time = pygame.time.get_ticks()
# В цикле
current_time = pygame.time.get_ticks()
if current_time - start_time > 1000:
print("Прошла секунда")
start_time = current_time

Функция pygame.time.delay(ms) приостанавливает программу на указанное время. pygame.time.wait(ms) работает аналогично, но блокирует события.


Изображения и звук

Модуль pygame.image загружает файлы в поверхности.

image = pygame.image.load("player.png")
# Конвертация формата для скорости
image = image.convert()
# Конвертация с прозрачностью
image = image.convert_alpha()

Поддерживемые форматы: PNG, JPG, BMP, GIF.

Модуль pygame.mixer управляет аудио.

Класс/ФункцияОписание
pygame.mixer.Sound(file)Загружает звуковой эффект в память
sound.play()Воспроизводит звук
sound.stop()Останавливает звук
sound.set_volume(val)Устанавливает громкость (0.0 - 1.0)
pygame.mixer.music.load(file)Загружает фоновую музыку (поток)
pygame.mixer.music.play(loops)Запускает музыку. loops=-1 для бесконечности
pygame.mixer.music.stop()Останавливает музыку
pygame.mixer.music.set_volume(val)Громкость музыки

Пример использования:

jump_sound = pygame.mixer.Sound("jump.wav")
jump_sound.play()

pygame.mixer.music.load("bgm.mp3")
pygame.mixer.music.play(-1)

Цикл игры (Game Loop)

Игровой цикл — фундаментальная структура любой интерактивной программы. В Pygame он реализуется вручную и состоит из трёх этапов:

  • Обработка событий (Event Handling)
  • Обновление состояния игры (State Update)
  • Отрисовка (Rendering) Пример базового цикла:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False

# Обновление логики
update_game_state()

# Отрисовка
screen.fill((0, 0, 0))
draw_objects(screen)
pygame.display.flip()

Цикл выполняется с максимально возможной частотой, обычно ограниченной с помощью pygame.time.Clock, чтобы обеспечить стабильный FPS (например, 60 кадров в секунду).


События (Events)

Pygame использует систему очереди событий. Все внешние воздействия — нажатия клавиш, движения мыши, закрытие окна — помещаются в очередь и обрабатываются в цикле через pygame.event.get().

События являются объектами с атрибутами type (тип события) и, опционально, дополнительными данными (key, pos, button и т.д.). Это позволяет реализовать реактивную архитектуру, где игра реагирует на пользовательский ввод асинхронно.

for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
player.jump()

Спрайты (Sprites)

Pygame использует объектно-ориентированную структуру. Использование классов упрощает управление множеством объектов. Класс наследуется от pygame.sprite.Sprite.

Базовый класс спрайта:

class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50, 50))
self.image.fill((0, 0, 255))
self.rect = self.image.get_rect()
self.rect.center = (400, 300)
self.speed = 5

def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT]:
self.rect.x += self.speed
if keys[pygame.K_UP]:
self.rect.y -= self.speed
if keys[pygame.K_DOWN]:
self.rect.y += self.speed

Спрайт в Pygame — это визуальный объект, обычно наследующий от класса pygame.sprite.Sprite. Спрайты объединяют графическое представление (Surface) и положение (Rect), а также могут содержать логику поведения.

Pygame предоставляет систему групп (pygame.sprite.Group) для эффективного управления коллекциями спрайтов: отрисовки, обновления, проверки столкновений.

class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50, 50))
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.rect.center = (400, 300)

def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.rect.x -= 5

Группировка спрайтов позволяет отделить логику от представления и упрощает управление сотнями объектов.

Класс pygame.sprite.Group хранит списки спрайтов. Позволяет обновлять и отрисовывать все объекты одним вызовом.

all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

# В цикле
all_sprites.update()
all_sprites.draw(screen)

Группы поддерживают проверку столкновений.

hits = pygame.sprite.spritecollide(player, enemies, False)

Параметр False указывает не удалять спрайт при столкновении.


Столкновения (Collisions)

Pygame предоставляет несколько методов для обнаружения пересечений между прямоугольниками (pygame.Rect). Основные функции:

  • rect1.colliderect(rect2) — проверяет пересечение двух прямоугольников.
  • pygame.sprite.collide_rect(sprite1, sprite2) — то же, для спрайтов.
  • pygame.sprite.spritecollide(sprite, group, dokill) — проверяет столкновение спрайта с группой.

Для более сложных форм используются маски (pygame.mask.from_surface()), обеспечивающие пиксельно-точное обнаружение столкновений, хотя и с большими вычислительными затратами.


Звуки и музыка

Pygame поддерживает воспроизведение аудиофайлов через модули pygame.mixer и pygame.mixer.music.

  • pygame.mixer.Sound — для коротких эффектов (выстрел, прыжок).
  • pygame.mixer.music — для фоновой музыки (поддерживает потоковое воспроизведение).
jump_sound = pygame.mixer.Sound("jump.wav")
jump_sound.play()

pygame.mixer.music.load("background.mp3")
pygame.mixer.music.play(-1) # зацикливание

Поддерживаются форматы WAV, MP3, OGG. Однако качество и стабильность воспроизведения зависят от платформы и бэкенда SDL.

Пример: реализация «Змейки». Это классический пример, демонстрирующий работу с циклом, вводом, отрисовкой и логикой столкновений.

Основные компоненты:

  • Состояние змейки: список координат её сегментов.
  • Направление движения: вектор (dx, dy).
  • Еда: случайно генерируемая позиция.
  • Условия завершения: выход за границы поля или самопересечение.

Полный код:

import pygame
import random

pygame.init()

white = (255, 255, 255)
black = (0, 0, 0)
red = (213, 50, 80)
green = (0, 255, 0)
blue = (50, 153, 213)

width = 600
height = 400

screen = pygame.display.set_mode((width, height))
pygame.display.set_caption('Змейка')

clock = pygame.time.Clock()
snake_block = 10
snake_speed = 15

font_style = pygame.font.SysFont("bahnschrift", 25)
score_font = pygame.font.SysFont("comicsansms", 35)

def your_score(score):
value = score_font.render("Счёт: " + str(score), True, black)
screen.blit(value, [0, 0])

def our_snake(snake_block, snake_list):
for x in snake_list:
pygame.draw.rect(screen, green, [x[0], x[1], snake_block, snake_block])

def message(msg, color):
mesg = font_style.render(msg, True, color)
screen.blit(mesg, [width / 6, height / 3])

def gameLoop():
game_over = False
game_close = False

x1 = width / 2
y1 = height / 2

x1_change = 0
y1_change = 0

snake_list = []
length_of_snake = 1

foodx = round(random.randrange(0, width - snake_block) / 10.0) * 10.0
foody = round(random.randrange(0, height - snake_block) / 10.0) * 10.0

while not game_over:

while game_close:
screen.fill(white)
message("Ты проиграл! C - продолжить, Q - выйти", red)
your_score(length_of_snake - 1)
pygame.display.update()

for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
game_over = True
game_close = False
if event.key == pygame.K_c:
gameLoop()

for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
x1_change = -snake_block
y1_change = 0
elif event.key == pygame.K_RIGHT:
x1_change = snake_block
y1_change = 0
elif event.key == pygame.K_UP:
y1_change = -snake_block
x1_change = 0
elif event.key == pygame.K_DOWN:
y1_change = snake_block
x1_change = 0

if x1 >= width or x1 < 0 or y1 >= height or y1 < 0:
game_close = True

x1 += x1_change
y1 += y1_change

screen.fill(white)
pygame.draw.rect(screen, red, [foodx, foody, snake_block, snake_block])

snake_head = [x1, y1]
snake_list.append(snake_head)

if len(snake_list) > length_of_snake:
del snake_list[0]

for x in snake_list[:-1]:
if x == snake_head:
game_close = True

our_snake(snake_block, snake_list)
your_score(length_of_snake - 1)

pygame.display.update()

if x1 == foodx and y1 == foody:
foodx = round(random.randrange(0, width - snake_block) / 10.0) * 10.0
foody = round(random.randrange(0, height - snake_block) / 10.0) * 10.0
length_of_snake += 1

clock.tick(snake_speed)

pygame.quit()
quit()

gameLoop()

Pygame — обучающая платформа, позволяющая освоить ключевые концепции геймдева: игровой цикл, обработку ввода, управление состоянием, коллизии и отрисовку. Он даёт контроль над каждым аспектом, что способствует глубокому пониманию механизмов, лежащих в основе интерактивных приложений.

Для серьёзных проектов рекомендуется использовать специализированные игровые движки: Godot, Unity, Unreal. Однако для обучения, прототипирования или реализации простых 2D-игр Python с Pygame остаётся практичным и доступным выбором.


Практические примеры реализации

Вывод текста на экран

Создание функции для централизованного вывода текста упрощает код.

def draw_text(surface, text, size, x, y):
font = pygame.font.SysFont("serif", size)
text_surface = font.render(text, True, (255, 255, 255))
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)
surface.blit(text_surface, text_rect)

Создание спрайта игрока с анимацией

Анимация реализуется через смену изображений по таймеру.

class AnimatedPlayer(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.frames = []
for i in range(4):
img = pygame.image.load(f"player_{i}.png").convert_alpha()
self.frames.append(img)
self.image = self.frames[0]
self.rect = self.image.get_rect()
self.frame_index = 0
self.last_update = 0
self.frame_rate = 100 # мс между кадрами

def update(self):
now = pygame.time.get_ticks()
if now - self.last_update > self.frame_rate:
self.last_update = now
self.frame_index = (self.frame_index + 1) % len(self.frames)
self.image = self.frames[self.frame_index]

Спрайт противника и движение

Противники двигаются автоматически вниз.

class Enemy(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((30, 30))
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.rect.x = random.randrange(0, 800)
self.rect.y = random.randrange(-100, -40)
self.speed_y = random.randrange(2, 8)

def update(self):
self.rect.y += self.speed_y
if self.rect.top > 600:
self.rect.x = random.randrange(0, 800)
self.rect.y = random.randrange(-100, -40)
self.speed_y = random.randrange(2, 8)

Платформа с коллизией

Проверка столкновения игрока с платформой требует проверки направления движения.

class Platform(pygame.sprite.Sprite):
def __init__(self, x, y, w, h):
super().__init__()
self.image = pygame.Surface((w, h))
self.image.fill((100, 100, 100))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y

# В цикле игрока
hits = pygame.sprite.spritecollide(player, platforms, False)
if hits:
if player.vel_y > 0: # Падение вниз
player.rect.bottom = hits[0].rect.top
player.vel_y = 0
player.jumping = False

Пуля или снаряд

Пули создаются при нажатии клавиши и летят вперёд.

class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((10, 5))
self.image.fill((255, 255, 0))
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.speed = 10

def update(self):
self.rect.x += self.speed
if self.rect.left > 800:
self.kill() # Удаляет спрайт из всех групп

# Стрельба
class Player(pygame.sprite.Sprite):
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
bullet = Bullet(self.rect.centerx, self.rect.centery)
all_sprites.add(bullet)
bullets.add(bullet)

Бонусы и сбор предметов

Бонусы исчезают при касании игрока.

class Bonus(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((20, 20))
self.image.fill((0, 255, 0))
self.rect = self.image.get_rect()
self.rect.x = random.randrange(0, 800)
self.rect.y = random.randrange(-100, -40)
self.speed_y = 3

def update(self):
self.rect.y += self.speed_y
if self.rect.top > 600:
self.kill()

# В главном цикле
hits = pygame.sprite.spritecollide(player, bonuses, True) # True удаляет бонус
for hit in hits:
score += 10

Реакция на клик мыши

Спавн объекта в месте клика.

for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Левая кнопка
pos = pygame.mouse.get_pos()
effect = Effect(pos[0], pos[1])
all_sprites.add(effect)

Передвижение спрайта с ускорением

Использование векторов скорости и ускорения.

class PhysicsPlayer(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50, 50))
self.image.fill((0, 0, 255))
self.rect = self.image.get_rect()
self.vel_x = 0
self.vel_y = 0
self.accel_x = 0
self.accel_y = 0
self.friction = -0.1

def update(self):
self.accel_x = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.accel_x = -0.5
if keys[pygame.K_RIGHT]:
self.accel_x = 0.5

self.accel_x += self.vel_x * self.friction
self.vel_x += self.accel_x
self.rect.x += self.vel_x

# Ограничение скорости
if self.vel_x > 10: self.vel_x = 10
if self.vel_x < -10: self.vel_x = -10

Вывод сообщений и экран Game Over

Отрисовка поверх игрового процесса.

def draw_game_over(screen):
font = pygame.font.SysFont("Arial", 64)
text = font.render("GAME OVER", True, (255, 0, 0))
rect = text.get_rect(center=(400, 300))
screen.blit(text, rect)

font_small = pygame.font.SysFont("Arial", 24)
text_small = font_small.render("Нажмите R для рестарта", True, (255, 255, 255))
rect_small = text_small.get_rect(center=(400, 350))
screen.blit(text_small, rect_small)

Включение и выключение звуков

Управление микшером через клавиши.

for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_m:
if pygame.mixer.music.get_busy():
pygame.mixer.music.pause()
else:
pygame.mixer.music.unpause()
if event.key == pygame.K_s:
volume = pygame.mixer.Sound.get_volume()
if volume > 0:
pygame.mixer.Sound.set_volume(0)
else:
pygame.mixer.Sound.set_volume(1)

Полный пример структуры игры

Код ниже объединяет описанные компоненты в единую систему.

import pygame
import random

WIDTH = 800
HEIGHT = 600
FPS = 60

# Цвета
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.speed = 5

def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT]:
self.rect.x += self.speed
if keys[pygame.K_UP]:
self.rect.y -= self.speed
if keys[pygame.K_DOWN]:
self.rect.y += self.speed

# Границы экрана
if self.rect.left < 0: self.rect.left = 0
if self.rect.right > WIDTH: self.rect.right = WIDTH
if self.rect.top < 0: self.rect.top = 0
if self.rect.bottom > HEIGHT: self.rect.bottom = HEIGHT

class Enemy(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((30, 30))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(WIDTH)
self.rect.y = random.randrange(-100, -40)
self.speed_y = random.randrange(2, 8)

def update(self):
self.rect.y += self.speed_y
if self.rect.top > HEIGHT:
self.rect.x = random.randrange(WIDTH)
self.rect.y = random.randrange(-100, -40)
self.speed_y = random.randrange(2, 8)

# Инициализация
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Игра")
clock = pygame.time.Clock()

# Группы
all_sprites = pygame.sprite.Group()
mobs = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

for i in range(8):
m = Enemy()
all_sprites.add(m)
mobs.add(m)

# Переменные
score = 0
font = pygame.font.SysFont("Arial", 24)
running = True

# Цикл
while running:
clock.tick(FPS)

# События
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False

# Обновление
all_sprites.update()

# Коллизии
hits = pygame.sprite.spritecollide(player, mobs, False)
if hits:
running = False

# Отрисовка
screen.fill(BLACK)
all_sprites.draw(screen)

# Интерфейс
score_text = font.render(f"Счёт: {score}", True, WHITE)
screen.blit(score_text, (10, 10))

pygame.display.flip()

pygame.quit()

Play

Библиотека play — сторонняя библиотека, разработанная для упрощения создания 2D-игр и анимаций, особенно в образовательной среде. Она строится на основе Pygame Zero, но предлагает ещё более минималистичный API, ориентированный на начинающих. Установка проста:

pip install play

play следует императивной модели, в которой программа описывает поведение объектов в виде функций обратного вызова (update, draw). Главный цикл управления выполняется автоматически.

Центральным элементом являются спрайты — двухмерные объекты, обладающие позицией, размером, изображением и свойствами анимации. Спрайт создаётся как экземпляр класса play.new_image() или play.new_box():

ball = play.new_circle(color="red", x=0, y=0, radius=25)

Каждый спрайт имеет атрибуты:

  • x, y — координаты центра.
  • angle — угол поворота.
  • size — масштаб относительно исходного изображения.
  • image — если используется текстура.
  • visible — видимость объекта.

Движение реализуется путём изменения атрибутов спрайтов в функции update(), которая вызывается каждый кадр (обычно 60 раз в секунду):

@play.repeat_forever
def do():
ball.x += 1

Также доступны встроенные методы, такие как move(), point_towards(), rotate().

Обнаружение столкновений (коллизий) выполняется с помощью метода is_touching(other_sprite):

if ball.is_touching(paddle):
ball.color = "blue"

Этот метод проверяет пересечение границ спрайтов (bounding boxes), что достаточно для большинства простых случаев, но не учитывает прозрачные области изображений.

play предоставляет простой доступ к состоянию клавиш:

if play.key_is_pressed("w"):
paddle.y += 5

Поддерживаются буквенные, цифровые и специальные клавиши ("space", "up", "left" и т. д.). Нет необходимости вручную управлять event loop — библиотека делает это автоматически.

Рассмотрим минимальную реализацию игры, где игрок должен перемещать платформу, чтобы поймать падающий шарик.

import play

# Создание объектов
paddle = play.new_box(color="green", x=0, y=-200, width=100, height=20)
ball = play.new_circle(color="red", x=0, y=200, radius=20)
score = play.new_text(words="Счёт: 0", x=0, y=250)

count = 0

@play.repeat_forever
def do():
global count

# Управление платформой
if play.key_is_pressed("left"):
paddle.x -= 5
if play.key_is_pressed("right"):
paddle.x += 5

# Движение шарика
ball.y -= 3

# Проверка коллизии
if ball.is_touching(paddle):
ball.y = 200 # Переместить шарик наверх
count += 1
score.words = f"Счёт: {count}"

# Перезапуск, если шарик упал
if ball.y < -250:
ball.y = 200
count = max(0, count - 1)
score.words = f"Счёт: {count}"

play.start_program()

Этот пример демонстрирует типичную структуру приложения в play: объявление объектов, реакция на события в цикле repeat_forever, использование глобального состояния.